package fem2;

import inf.text.ArrayFormat;

import java.util.ArrayList;

import fem2.enu.SortingCriteria;

/**
 * The <code> Mesh</code> class connects all the mesh parts
 * 
 * @author hbui
 */

public class Mesh {

	protected int nDofs = 0;
	protected int d = 1;

	private ArrayList<Node> nodes = new ArrayList<Node>();
	private ArrayList<Edge> edges = new ArrayList<Edge>();
	private ArrayList<Face> faces = new ArrayList<Face>();
	private ArrayList<Block> blocks = new ArrayList<Block>();

	private Topology topology = null;

	/**
	 * @return mesh topology
	 */
	public Topology getTopology() {
		return topology;
	}

	/**
	 * set a topology for this mesh
	 * 
	 * @param topology
	 */
	public void setTopology(Topology topology) {
		this.topology = topology;
	}

	/**
	 * Adds a node to the mesh
	 * 
	 * @param n
	 *            the node needs to be added.
	 * @return added node.
	 */
	public Node addNode(Node n) {
		this.nodes.add(n);
		n.setId(nodes.size());
		return n;
	}

	/**
	 * remove node at i-th index. Use it with care since the node sequence in
	 * array list s not maintained. Note: let remove from higher index node to
	 * lower index node since it will happen in correct order
	 * 
	 * @param i
	 */
	public void removeNode(int i) {
		nodes.remove(i);
	}

	/**
	 * Adds an edge to the mesh
	 * 
	 * @param n
	 *            the node needs to be added.
	 * @return added node.
	 */
	public Edge addEdge(Edge e) {
		this.edges.add(e);
		e.setId(edges.size());
		return e;
	}

	/**
	 * Adds a face to the mesh
	 * 
	 * @param f
	 *            the face needs to be added.
	 * @return added face.
	 */
	public Face addFace(Face f) {
		this.faces.add(f);
		f.setId(faces.size());
		return f;
	}

	/**
	 * Adds a block to the mesh
	 * 
	 * @param b
	 *            the block needs to be added.
	 * @return added block.
	 */
	public Block addBlock(Block b) {
		this.blocks.add(b);
		b.setId(blocks.size());
		return b;
	}

	/**
	 * @return dimension of space the mesh is acting on
	 */
	public int getDimension() {
		return getNode(0).getDimension();
	}

	/**
	 * Returns the number of degrees of freedom in the mesh.
	 * 
	 * @return the number of degrees of freedom.
	 */
	public int countDofs() {
		return this.nDofs;
	}

	/**
	 * Returns the number of blocks in mesh
	 * 
	 * @return number of blocks.
	 */
	public int countBlocks() {
		return this.blocks.size();
	}

	/**
	 * Returns the number of faces in mesh
	 * 
	 * @return number of faces.
	 */
	public int countFaces() {
		return this.faces.size();
	}

	/**
	 * Returns the number of edges in mesh
	 * 
	 * @return number of edges.
	 */
	public int countEdges() {
		return this.edges.size();
	}

	/**
	 * Returns the number of nodes in mesh
	 * 
	 * @return number of nodes.
	 */
	public int countNodes() {
		return this.nodes.size();
	}

	/**
	 * Get number of fields.
	 * 
	 * 
	 */
	public int countFields() {
		return this.d;
	}

	/**
	 * Enumerates DOFs for all nodes, edges and faces in mesh.
	 * 
	 */
	public void enumerateDofs() {
		this.nDofs = 0;
		for (int i = 0; i < countNodes(); i++) {
			this.nDofs = getNode(i).enumerateDofs(this.nDofs);
		}
		for (Edge e : edges) {
			e.enumerateDofs();
		}
		for (Face f : faces) {
			f.enumerateDofs();
		}
		for (Block b : blocks) {
			b.enumerateDofs();
		}
	}

	/**
	 * Gets a block from mesh.
	 * 
	 * @param i
	 *            the index of block.
	 * @return the block corresponding to given index.
	 */
	public Block getBlock(int i) {
		return this.blocks.get(i);
	}

	/**
	 * Gets a face from mesh.
	 * 
	 * @param i
	 *            the index of face.
	 * @return the face corresponding to given index.
	 */
	public Face getFace(int i) {
		return this.faces.get(i);
	}

	/**
	 * Gets an edge from mesh.
	 * 
	 * @param i
	 *            the index of edge.
	 * @return the edge corresponding to given index.
	 */
	public Edge getEdge(int i) {
		return this.edges.get(i);
	}

	/**
	 * Gets an node from mesh.
	 * 
	 * @param i
	 *            the index of node.
	 * @return the node corresponding to given index.
	 */
	public Node getNode(int i) {
		return this.nodes.get(i);
	}

	/**
	 * Sets number of fields.
	 * 
	 * @param d
	 */
	public void setNumberOfFields(int d) {
		this.d = d;
		for (Node n : nodes) {
			n.setNumberOfFields(this.d);
		}
		for (Edge e : edges) {
			e.setNumberOfFields(this.d);
		}
		for (Face f : faces) {
			f.setNumberOfFields(this.d);
		}
		for (Block b : blocks) {
			b.setNumberOfFields(this.d);
		}
	}

	/**
	 * Sets global solution for all mesh parts at time t.
	 * 
	 * @param t
	 *            time
	 * @param uHatGlobal
	 *            array of double, which is the global solution.
	 */
	public void setUHatGlobal(double t, double[] uHatGlobal) {
		for (Node n : nodes) {
			n.setUHatGlobal(t, uHatGlobal);
		}
		for (Edge e : edges) {
			e.setUHatGlobal(t, uHatGlobal);
		}
		for (Face f : faces) {
			f.setUHatGlobal(t, uHatGlobal);
		}
		for (Block b : blocks) {
			b.setUHatGlobal(t, uHatGlobal);
		}
	}

	/**
	 * retrieve global solution from local solution
	 * 
	 * @param uHatGlobal
	 */
	public void copyUHatGlobal(double[] uHatGlobal) {
		for (int i = 0; i < countNodes(); i++) {
			double[] uHatLocal = getNode(i).getUHatLocal();
			int[] dofs = getNode(i).getDofNumbers();
			for (int j = 0; j < dofs.length; j++) {
				if (dofs[j] != -1) {
					uHatGlobal[dofs[j]] = uHatLocal[j];
				}
			}
		}
	}

	/**
	 * utility method to assign same constraint to a list of nodes
	 * 
	 * @param c
	 * @param nodes
	 */
	public void setConstraint(Constraint c, ArrayList<Node> nodes) {
		for (Node n : nodes) {
			n.setConstraint(c);
		}
	}

	/**
	 * utility method to assign same constraint to aan array of nodes
	 * 
	 * @param c
	 * @param nodes
	 */
	public void setConstraint(Constraint c, Node[] nodes) {
		for (Node n : nodes) {
			n.setConstraint(c);
		}
	}

	/**
	 * utility method to add the same constraint to a list of nodes
	 * 
	 * @param c
	 * @param nodes
	 */
	public void addConstraint(Constraint c, ArrayList<Node> nodes) {
		for (Node n : nodes) {
			n.addConstraint(c);
		}
	}

	/**
	 * utility method to assign same sorting criteria to a list of nodes
	 * 
	 * @param c
	 * @param nodes
	 */
	public void setSortingCriteria(SortingCriteria criteria, ArrayList<Node> nodes) {
		for (Node n : nodes) {
			n.setSortingCriteria(criteria);
		}
	}

	/**
	 * print the mesh information: nodes, edges, faces,...
	 */
	public void Print() {
		/*
		 * print nodes
		 */
		System.out.println("number of nodes: " + countNodes());
		for (int i = 0; i < countNodes(); i++) {
			Node n = getNode(i);
			System.out.println(ArrayFormat.format(n.getPosition()));
		}
		/*
		 * print edges
		 */
		System.out.println("number of edges: " + countEdges());
		for (int i = 0; i < countEdges(); i++) {
			Edge e = getEdge(i);
			Node[] n = e.getNodes();
			System.out.println("edge " + i + " :");
			for (int j = 0; j < n.length; j++) {
				System.out.println(ArrayFormat.format(n[j].getPosition()));
			}
		}
		/*
		 * print faces
		 */
		System.out.println("number of faces: " + countFaces());
		for (int i = 0; i < countFaces(); i++) {
			Face f = getFace(i);
			Node[] n = f.getNodes();
			System.out.println("face " + i + " :");
			for (int j = 0; j < n.length; j++) {
				System.out.println(ArrayFormat.format(n[j].getPosition()));
			}
		}
		/*
		 * print blocks
		 */
		System.out.println("number of blocks: " + countBlocks());
		for (int i = 0; i < countBlocks(); i++) {
			Block b = getBlock(i);
			Node[] n = b.getNodes();
			System.out.println("block " + i + " :");
			for (int j = 0; j < n.length; j++) {
				System.out.println(ArrayFormat.format(n[j].getPosition()));
			}
		}
	}
}
